//
// Created by tang on 23-4-1.
//

#include "ui_style.hpp"
#include <cstddef>
#include <functional>
#include <regex>
#include <string>
#include <unordered_map>
#include "core/log.hpp"
#include "imgui.h"
#include "utils/string_utils.hpp"
#include "color_parser.hpp"

static std::string get_defined_value(const std::string &input)
{
    using rep_fn = std::function<std::string()>;
    static std::unordered_map<std::string, rep_fn> patterns {
        {"FS",[]{ return std::to_string(ImGui::GetFontSize()); }},
        {"HFS",[]{ return std::to_string(ImGui::GetFontSize()/2.f); }},
        {"LH",[]{ return std::to_string(ImGui::GetTextLineHeightWithSpacing()); }},
        {"HLH",[]{ return std::to_string(ImGui::GetTextLineHeightWithSpacing()/2.f); }},
    };

    auto it = patterns.find(input);
    if (it == end(patterns)) {
        return input;
    } else {
        return it->second();
    }
}

static std::string eval_string(const std::string &input) {
    std::string output;

    size_t last, pos;
    last = pos = 0;
    while (pos < input.length())
    {
        pos = input.find("@{", last);
        if (pos != last || last == 0) {
            output += input.substr(last, pos - last);
            if (pos == std::string::npos) {
                break;
            }
            last = pos + 2; // strlen("@{")
        }
        pos = input.find('}', last);
        if (last != pos) {
            output += get_defined_value(input.substr(last, pos - last));
            if (pos == std::string::npos) {
                break;
            }
            last = pos + 1; // strlen("}")
        }
    }
    return output;
}

void ui_style::use () const
{
    // apply all styles, then apply self styles
    for (auto const &x: vars_all)
    {
        if (x.second.is_float)
        {
            ImGui::PushStyleVar (x.first, x.second.f_value);
        }
        else
        {
            ImGui::PushStyleVar (x.first, x.second.v2_value);
        }
    }
    for (auto const &x: colors_all)
    {
        ImGui::PushStyleColor (x.first, x.second);
    }

    for (auto const &x: vars_self)
    {
        if (x.second.is_float)
        {
            ImGui::PushStyleVar (x.first, x.second.f_value);
        }
        else
        {
            ImGui::PushStyleVar (x.first, x.second.v2_value);
        }
    }
    for (auto const &x: colors_self)
    {
        ImGui::PushStyleColor (x.first, x.second);
    }
}

void ui_style::revert (style_mode mode) const
{
    if (mode == style_mode::self_only)
    {
        ImGui::PopStyleColor (static_cast<int> (colors_self.size ()));
        ImGui::PopStyleVar (static_cast<int> (vars_self.size ()));
    }
    else if (mode == style_mode::with_child)
    {
        ImGui::PopStyleColor (static_cast<int> (colors_all.size ()));
        ImGui::PopStyleVar (static_cast<int> (vars_all.size ()));
    }
}

ui_style *ui_style::create_from_node (const pugi::xml_node &node)
{
    auto *style = new ui_style;

    if (!style->init (node))
    {
        delete style;
        style = nullptr;
    }

    return style;
}

bool ui_style::init (const pugi::xml_node &node)
{
    bool ok = false;
    // find child node <style>, default mode is with_child
    if (const auto style_node = node.child ("styles"))
    {
        for (auto &item: style_node)
        {
            const std::string key = item.attribute ("key").as_string ();
            const std::string val = item.attribute ("value").as_string ();
            const std::string self = item.attribute("self").as_string();
            if (!key.empty () && !val.empty ())
                if (append_style (key, val, !self.empty ()))
                {
                    ok = true;
                }
        }
    }
    // use styles attribute, mode always be self only
    const std::string style_attr = node.attribute ("styles").as_string ();
    if (!style_attr.empty ())
    {
        auto items = split_string(style_attr, ";;");
        for (auto const &item: items)
        {
            auto kv = split_any(item, ":=");
            if (kv.size () == 2)
            {
                if (append_style(kv[0], kv[1], true))
                    ok = true;
            }
        }
    }
    return ok;
}

bool ui_style::append_style (const std::string &k, const std::string &v, bool self_only)
{
    struct style_info
    {
        s32 is_color : 1; // 1 -> StyleColor, 0 -> StyleVar
        s32 is_float : 1; // 当 is_color 为 0 时有用，0 -> ImVec2, 1 -> float
        s32 idx : 30;     // 索引
    };

    static std::unordered_map<std::string, style_info> s_style_info_map = {
            // colors
            {"text", {1, 0, ImGuiCol_Text}},
            {"text_disabled", {1, 0, ImGuiCol_TextDisabled}},
            {"window_bg", {1, 0, ImGuiCol_WindowBg}},
            {"child_bg", {1, 0, ImGuiCol_ChildBg}},
            {"popup_bg", {1, 0, ImGuiCol_PopupBg}},
            {"border", {1, 0, ImGuiCol_Border}},
            {"border_shadow", {1, 0, ImGuiCol_BorderShadow}},
            {"frame_bg", {1, 0, ImGuiCol_FrameBg}},
            {"frame_bg_hovered", {1, 0, ImGuiCol_FrameBgHovered}},
            {"frame_bg_active", {1, 0, ImGuiCol_FrameBgActive}},
            {"title_bg", {1, 0, ImGuiCol_TitleBg}},
            {"title_bg_active", {1, 0, ImGuiCol_TitleBgActive}},
            {"title_bg_collapsed", {1, 0, ImGuiCol_TitleBgCollapsed}},
            {"menu_bar_bg", {1, 0, ImGuiCol_MenuBarBg}},
            {"scrollbar_bg", {1, 0, ImGuiCol_ScrollbarBg}},
            {"scrollbar_grab", {1, 0, ImGuiCol_ScrollbarGrab}},
            {"scrollbar_grab_hovered", {1, 0, ImGuiCol_ScrollbarGrabHovered}},
            {"scrollbar_grab_active", {1, 0, ImGuiCol_ScrollbarGrabActive}},
            {"check_mark", {1, 0, ImGuiCol_CheckMark}},
            {"slider_grab", {1, 0, ImGuiCol_SliderGrab}},
            {"slider_grab_active", {1, 0, ImGuiCol_SliderGrabActive}},
            {"button", {1, 0, ImGuiCol_Button}},
            {"button_hovered", {1, 0, ImGuiCol_ButtonHovered}},
            {"button_active", {1, 0, ImGuiCol_ButtonActive}},
            {"header", {1, 0, ImGuiCol_Header}},
            {"header_hovered", {1, 0, ImGuiCol_HeaderHovered}},
            {"header_active", {1, 0, ImGuiCol_HeaderActive}},
            {"separator", {1, 0, ImGuiCol_Separator}},
            {"separator_hovered", {1, 0, ImGuiCol_SeparatorHovered}},
            {"separator_active", {1, 0, ImGuiCol_SeparatorActive}},
            {"resize_grip", {1, 0, ImGuiCol_ResizeGrip}},
            {"resize_grip_hovered", {1, 0, ImGuiCol_ResizeGripHovered}},
            {"resize_grip_active", {1, 0, ImGuiCol_ResizeGripActive}},
            {"tab", {1, 0, ImGuiCol_Tab}},
            {"tab_hovered", {1, 0, ImGuiCol_TabHovered}},
            {"tab_active", {1, 0, ImGuiCol_TabActive}},
            {"tab_unfocused", {1, 0, ImGuiCol_TabUnfocused}},
            {"tab_unfocused_active", {1, 0, ImGuiCol_TabUnfocusedActive}},
            {"docking_preview", {1, 0, ImGuiCol_DockingPreview}},
            {"docking_empty_bg", {1, 0, ImGuiCol_DockingEmptyBg}},
            {"plot_lines", {1, 0, ImGuiCol_PlotLines}},
            {"plot_lines_hovered", {1, 0, ImGuiCol_PlotLinesHovered}},
            {"plot_histogram", {1, 0, ImGuiCol_PlotHistogram}},
            {"plot_histogram_hovered", {1, 0, ImGuiCol_PlotHistogramHovered}},
            {"table_header_bg", {1, 0, ImGuiCol_TableHeaderBg}},
            {"table_border_strong", {1, 0, ImGuiCol_TableBorderStrong}},
            {"table_border_light", {1, 0, ImGuiCol_TableBorderLight}},
            {"table_row_bg", {1, 0, ImGuiCol_TableRowBg}},
            {"table_row_bg_alt", {1, 0, ImGuiCol_TableRowBgAlt}},
            {"text_selected_bg", {1, 0, ImGuiCol_TextSelectedBg}},
            {"drag_drop_target", {1, 0, ImGuiCol_DragDropTarget}},
            {"nav_highlight", {1, 0, ImGuiCol_NavHighlight}},
            {"nav_windowing_highlight", {1, 0, ImGuiCol_NavWindowingHighlight}},
            {"nav_windowing_dim_bg", {1, 0, ImGuiCol_NavWindowingDimBg}},
            {"modal_window_dim_bg", {1, 0, ImGuiCol_ModalWindowDimBg}},

            // vars
            {"alpha", {0, 1, ImGuiStyleVar_Alpha}},                                        // float     Alpha
            {"disabled_alpha", {0, 1, ImGuiStyleVar_DisabledAlpha}},                       // float     DisabledAlpha
            {"window_padding", {0, 0, ImGuiStyleVar_WindowPadding}},                       // ImVec2    WindowPadding
            {"window_rounding", {0, 1, ImGuiStyleVar_WindowRounding}},                     // float     WindowRounding
            {"window_border_size", {0, 1, ImGuiStyleVar_WindowBorderSize}},                // float     WindowBorderSize
            {"window_min_size", {0, 0, ImGuiStyleVar_WindowMinSize}},                      // ImVec2    WindowMinSize
            {"window_title_align", {0, 0, ImGuiStyleVar_WindowTitleAlign}},                // ImVec2    WindowTitleAlign
            {"child_rounding", {0, 1, ImGuiStyleVar_ChildRounding}},                       // float     ChildRounding
            {"child_border_size", {0, 1, ImGuiStyleVar_ChildBorderSize}},                  // float     ChildBorderSize
            {"popup_rounding", {0, 1, ImGuiStyleVar_PopupRounding}},                       // float     PopupRounding
            {"popup_border_size", {0, 1, ImGuiStyleVar_PopupBorderSize}},                  // float     PopupBorderSize
            {"frame_padding", {0, 0, ImGuiStyleVar_FramePadding}},                         // ImVec2    FramePadding
            {"frame_rounding", {0, 1, ImGuiStyleVar_FrameRounding}},                       // float     FrameRounding
            {"frame_border_size", {0, 1, ImGuiStyleVar_FrameBorderSize}},                  // float     FrameBorderSize
            {"item_spacing", {0, 0, ImGuiStyleVar_ItemSpacing}},                           // ImVec2    ItemSpacing
            {"item_inner_spacing", {0, 0, ImGuiStyleVar_ItemInnerSpacing}},                // ImVec2    ItemInnerSpacing
            {"indent_spacing", {0, 1, ImGuiStyleVar_IndentSpacing}},                       // float     IndentSpacing
            {"cell_padding", {0, 0, ImGuiStyleVar_CellPadding}},                           // ImVec2    CellPadding
            {"scrollbar_size", {0, 1, ImGuiStyleVar_ScrollbarSize}},                       // float     ScrollbarSize
            {"scrollbar_rounding", {0, 1, ImGuiStyleVar_ScrollbarRounding}},               // float     ScrollbarRounding
            {"grab_min_size", {0, 1, ImGuiStyleVar_GrabMinSize}},                          // float     GrabMinSize
            {"grab_rounding", {0, 1, ImGuiStyleVar_GrabRounding}},                         // float     GrabRounding
            {"tab_rounding", {0, 1, ImGuiStyleVar_TabRounding}},                           // float     TabRounding
            {"button_text_align", {0, 0, ImGuiStyleVar_ButtonTextAlign}},                  // ImVec2    ButtonTextAlign
            {"selectable_text_align", {0, 0, ImGuiStyleVar_SelectableTextAlign}},          // ImVec2    SelectableTextAlign
            {"separator_text_border_size", {0, 1, ImGuiStyleVar_SeparatorTextBorderSize}}, // float  SeparatorTextBorderSize
            {"separator_text_align", {0, 0, ImGuiStyleVar_SeparatorTextAlign}},            // ImVec2    SeparatorTextAlign
            {"separator_text_padding", {0, 0, ImGuiStyleVar_SeparatorTextPadding}},        // ImVec2    SeparatorTextPadding
    };

    const auto it = s_style_info_map.find(to_lower(k));
    if (it == end(s_style_info_map))
    {
        L_WARN("Unknown style key: %s", k.c_str());
        return false;
    }

    auto const info = it->second;
    if (info.is_color)
    {
        decltype(colors_all)* container = self_only ? &colors_self : &colors_all;
        auto color_opt = CSSColorParser::parse(v);
        if (!color_opt.has_value ())
            return false;
        container->emplace(info.idx, color_opt.value ());
    }
    else
    {
        std::string evaluated = eval_string(v);
        decltype(vars_all)* container = self_only ? &vars_self : &vars_all;
        var_value value{false, {}};
        if (info.is_float)
        {
            value.is_float = true;

            char *end = nullptr;
            value.f_value = (float)strtod(evaluated.c_str(), &end);
            if (end == nullptr)
            {
                L_WARN("Style %s: bad value %s", k.c_str(), evaluated.c_str());
                return false;
            }
        }
        else
        {
            value.is_float = false;
            
            auto values = split_any(evaluated, ",|;");
            if (values.empty()) {
                L_WARN("Style %s: value is empty!", evaluated.c_str());
                return false;
            } else if (values.size() == 1) {
                char *end = nullptr;
                float v = (float)strtod(values[0].c_str(), &end);
                if (end == nullptr)
                {
                    L_INFO("Style %s: can not parse value %s as a float value", k.c_str(), values[0].c_str());
                    return false;
                }
                value.v2_value = ImVec2{v,v};
            } else if (values.size() == 2) {
                char *end = nullptr;
                float v1 = (float)strtod(values[0].c_str(), &end);
                if (end == nullptr)
                {
                    L_INFO("Style %s: can not parse value %s as a float value", k.c_str(), values[0].c_str());
                    return false;
                }
                float v2 = (float)strtod(values[1].c_str(), &end);
                if (end == nullptr)
                {
                    L_INFO("Style %s: can not parse value %s as a float value", k.c_str(), values[1].c_str());
                    return false;
                }
                value.v2_value = ImVec2{v1,v2};
            } else {
                L_WARN("Style %s: too many values %s", k.c_str(), evaluated.c_str());
            }
        }
        container->emplace(info.idx, value);
    }

    return true;
}

void ui_options::use ()
{
    if (style_width > 0) {
        ImGui::PushItemWidth(style_width);
    }
}

void ui_options::revert ()
{
    if (style_width > 0) {
        ImGui::PopItemWidth();
    }
}

ui_options *ui_options::create_from_node (const pugi::xml_node &node)
{
    auto* opt = new ui_options;

    if (!opt->init(node))
    {
        delete opt;
        opt = nullptr;
    }

    return opt;
}

bool ui_options::init (const pugi::xml_node &node)
{
    std::string opt = node.attribute("tweaks").as_string();
    if (!opt.empty())
    {
        auto items = split_string(opt, ";;");
        bool ok = false;
        for (auto const &item: items)
        {
            auto kv = split_any(item, ":=");
            if (kv.size () == 2)
            {
                if (kv[0] == "width") {
                    char *end = nullptr;
                    style_width = strtod(kv[1].c_str(), &end);
                    ok = style_width > 0.f;
                }
            }
        }
        return ok;
    }
    return false;
}
